# Load packages
library(DESeq2) # ‘1.46.0’
library(dplyr)
library(ggplot2)
library(org.Hs.eg.db) # library(org.Mm.eg.db) for mouse
library(ComplexHeatmap)
library(circlize)   # for color scales
library(grid)       # for gpar()
library(EnhancedVolcano)
# Load count_matrix
args(read.delim) # header = TRUE by default
function (file, header = TRUE, sep = "\t", quote = "\"", dec = ".", 
    fill = TRUE, comment.char = "", ...) 
NULL
rawCounts <- read.delim("http://genomedata.org/gen-viz-workshop/intro_to_deseq2/tutorial/E-GEOD-50760-raw-counts.tsv")
# Format rawCounts as required by DESeq2
# 1. Set gene ID column as rowname
rownames(rawCounts) <- rawCounts$Gene.ID

# DESeq2 expects the rawCounts to contain only raw, unnormalized integer values, with no non-numeric columns.
# 2. Remove columns with non numerical values 
rawCounts_bkup <- rawCounts
rawCounts <- rawCounts[, -c(1, 2)]

class(rawCounts) # a data.frame
[1] "data.frame"
# 3. Convert rawCounts as a matrix
rawCounts <- as.matrix(rawCounts)
# Note: we can do both steps above in one line of code
# rawCounts <- as.matrix(rawCounts[, sapply(rawCounts, is.numeric)])
# checks
is.matrix(rawCounts) # should be TRUE
is.numeric(rawCounts) # should be TRUE
# # Filtering: remove genes with 0 reads in all samples.
dim(rawCounts)
[1] 65217    54
dim(rawCounts[which(rowSums(rawCounts)>0),])
[1] 44316    54
rawCounts <- rawCounts[which(rowSums(rawCounts)>0),]
# Load colData (a dataframe describing samples)
colData <- read.delim("http://genomedata.org/gen-viz-workshop/intro_to_deseq2/tutorial/E-GEOD-50760-experiment-design.tsv")
# Format colData as required by DESeq2
# 1. Set sample ID (here Run column) as rowname
rownames(colData) <- colData$Run

# 2. Remove unnecessary columns
# Tissue type is the primary condition of interest
# Individual IDs are treated as control variables to account for inter-individual variation
keep <- c("Sample.Characteristic.biopsy.site.", "Sample.Characteristic.individual.")
colData <- colData[, keep]

# 3. Rename columns
colnames(colData) <- c("tissue_type", "individual_id")

#  4. Verify that the sample IDs match exactly and are in the same order between rawCounts and colData
all(rownames(colData) == colnames(rawCounts)) # should return TRUE
[1] TRUE
head(colData)
# 5. convert both columns of colData as factors
class(colData$individual_id) # "character"
[1] "character"
colData$individual_id <- factor(colData$individual_id)
class(colData$individual_id) # "factor"
[1] "factor"
colData$tissue_type <- factor(colData$tissue_type)
# DESeq2 uses the first factor level as reference
levels(colData$tissue_type)  # reference = "colorectal cancer metastatic in the liver"
[1] "colorectal cancer metastatic in the liver"
[2] "normal"                                   
[3] "primary tumor"                            
# Comparisons: "normal" and "primary tumor" vs reference
# We want to compare "colorectal cancer metastatic in the liver" and  "primary tumor" against "normal" instead
# 6. Change levels order such that  reference = "normal"
levels(colData$tissue_type) <- c(
  "normal",
  "primary tumor",
  "colorectal cancer metastatic in the liver"
)
levels(colData$tissue_type)
[1] "normal"                                   
[2] "primary tumor"                            
[3] "colorectal cancer metastatic in the liver"
# Create the DEseq2DataSet object
# Design: control variable first, condition of interest second
# DESeq2 adjusts for individual variation before testing tissue_type differences
dds <- DESeqDataSetFromMatrix(countData = rawCounts, colData = colData, design = ~ individual_id + tissue_type)
dds # prints summary of the DESeqDataSet
class: DESeqDataSet 
dim: 44316 54 
metadata(1): version
assays(1): counts
rownames(44316): ENSG00000000003 ENSG00000000005 ... ENSG00000281918
  ENSG00000281920
rowData names(0):
colnames(54): SRR975551 SRR975552 ... SRR975603 SRR975604
colData names(2): tissue_type individual_id
colData(dds)  # shows sample annotations (e.g., tissue_type, individual_id)
DataFrame with 54 rows and 2 columns
                                        tissue_type individual_id
                                           <factor>      <factor>
SRR975551 colorectal cancer metastatic in the liver         AMC_2
SRR975552 colorectal cancer metastatic in the liver         AMC_3
SRR975553 colorectal cancer metastatic in the liver         AMC_5
SRR975554 colorectal cancer metastatic in the liver         AMC_6
SRR975555 colorectal cancer metastatic in the liver         AMC_7
...                                             ...           ...
SRR975600                                    normal        AMC_20
SRR975601                                    normal        AMC_21
SRR975602                                    normal        AMC_22
SRR975603                                    normal        AMC_23
SRR975604                                    normal        AMC_24
counts(dds)[1:5, 1:5]  # view first few genes and samples
                SRR975551 SRR975552 SRR975553 SRR975554 SRR975555
ENSG00000000003      6617      1352      1492      3390      1464
ENSG00000000005        69         1        20        23        12
ENSG00000000419      2798       714       510      1140      1667
ENSG00000000457       486       629       398       239       383
ENSG00000000460       466       342        73       227       193

#  Run the DESeq pipeline
dds <- DESeq(dds)
# Variance Stabilizing Transformation and  plot PCA
vsd <- vst(dds, blind = FALSE)
plotPCA(vsd, intgroup="tissue_type")+ theme_grey()

# Note: contrast = c(factor, numerator, denominator)
# means Compute log2 fold change as numerator / denominator.
# contrast = c("tissue_type", "primary tumor", "normal"): positive values mean higher in tumor, negative mean higher in normal
# contrast = c("tissue_type", "normal", "primary tumor"): positive values mean higher in normal, negative mean higher in tumor
# Extract results
res1 <- results(dds, contrast = c("tissue_type", "primary tumor", "normal"))
res1
log2 fold change (MLE): tissue_type primary tumor vs normal 
Wald test p-value: tissue type primary.tumor vs normal 
DataFrame with 44316 rows and 6 columns
                  baseMean log2FoldChange     lfcSE      stat      pvalue        padj
                 <numeric>      <numeric> <numeric> <numeric>   <numeric>   <numeric>
ENSG00000000003   1824.881      -0.441529  0.180629  -2.44439 1.45095e-02 3.48599e-02
ENSG00000000005     10.852       0.540095  0.435418   1.24040 2.14826e-01 3.30104e-01
ENSG00000000419    725.173      -1.009012  0.135824  -7.42881 1.09583e-13 3.96955e-12
ENSG00000000457    311.358       0.321168  0.113114   2.83934 4.52069e-03 1.26684e-02
ENSG00000000460    126.362      -1.357771  0.211021  -6.43429 1.24048e-10 2.26492e-09
...                    ...            ...       ...       ...         ...         ...
ENSG00000281909  0.2767389      0.3742786  2.936548 0.1274553    0.898580          NA
ENSG00000281910  0.0537801      0.0416978  3.668177 0.0113674    0.990930          NA
ENSG00000281912 10.2974190      0.1583833  0.227051 0.6975657    0.485449    0.614118
ENSG00000281918  0.2310112      0.2723037  3.664070 0.0743173    0.940758          NA
ENSG00000281920  0.2112458      0.0399116  3.666410 0.0108857    0.991315          NA
# Remove rows with NA 
res1 <- na.omit(res1)
res1.df <- as.data.frame(res1)
res1.df
# get gene names for gene IDs
res1.df$symbol <- mapIds(org.Hs.eg.db, keys = rownames(res1.df), keytype = "ENSEMBL", column = "SYMBOL")
res1.df <- res1.df[!is.na(res1.df$symbol),]
res2 <- results(dds, contrast = c("tissue_type", "colorectal cancer metastatic in the liver", "normal"))
res2 <- na.omit(res2)
res2.df <- as.data.frame(res2)
res2.df
res2.df$symbol <- mapIds(org.Hs.eg.db, keys = rownames(res2.df), keytype = "ENSEMBL", column = "SYMBOL")
res2.df <- res2.df[!is.na(res2.df$symbol),]
# Volcano plots 1
EnhancedVolcano(res1.df, x = 'log2FoldChange', y = 'padj', lab = res1.df$symbol,
                pCutoff = 0.05, FCcutoff = 1)

# Volcano plots 2
EnhancedVolcano(res2.df, x = 'log2FoldChange', y = 'padj', lab = res2.df$symbol,
                pCutoff = 0.05, FCcutoff = 1)

# Volcano plots using ggplot2

res1.df$expression <- "NOT"
res1.df$expression[res1.df$padj < 0.01 & res1.df$log2FoldChange > 2] <- "UP"
res1.df$expression[res1.df$padj < 0.01 & res1.df$log2FoldChange < -2] <- "DOWN"

ggplot(data = res1.df, aes(x = log2FoldChange, y = -log10(padj), col = expression)) +
  geom_vline(xintercept = c(-2, 2), col = "gray", linetype = 'dashed') +
  geom_hline(yintercept = -log10(0.01), col = "gray", linetype = 'dashed') +
  geom_point(size = 2) +
  scale_color_manual(values = c("#3498DB", "grey", "#E74C3C"), 
                     labels = c("Downregulated", "Not significant", "Upregulated")) +
  theme_classic()

res2.df$expression <- "NOT"
res2.df$expression[res2.df$padj < 0.01 & res2.df$log2FoldChange > 2] <- "UP"
res2.df$expression[res2.df$padj < 0.01 & res2.df$log2FoldChange < -2] <- "DOWN"

ggplot(data = res2.df, aes(x = log2FoldChange, y = -log10(padj), col = expression)) +
  geom_vline(xintercept = c(-2, 2), col = "gray", linetype = 'dashed') +
  geom_hline(yintercept = -log10(0.01), col = "gray", linetype = 'dashed') +
  geom_point(size = 2) +
  scale_color_manual(values = c("#3498DB", "grey", "#E74C3C"), 
                     labels = c("Downregulated", "Not significant", "Upregulated")) +
  theme_classic()

# Find significant genes
sigs1.df <- res1.df[res1.df$padj < 0.01,]
sigs1.df <- sigs1.df[(abs(sigs1.df$log2FoldChange) > 2),]
sigs1.df <- sigs1.df[sigs1.df$baseMean > 50,] # -  average normalized count for a gene across all samples
sigs1.df
NA
NA
sigs2.df <- res2.df[res2.df$padj < 0.01,]
sigs2.df <- sigs2.df[(abs(sigs2.df$log2FoldChange) > 2),]
sigs2.df <- sigs2.df[sigs2.df$baseMean > 50,] 
sigs2.df
# Combine significant genes from both comparisons
sigs_combined <- rbind(sigs1.df, sigs2.df)
# Get unique gene IDs
unique_genes <- unique(c(rownames(sigs1.df), rownames(sigs2.df)))
# Subset by unique rownames
sigs_combined <- sigs_combined[unique_genes, ]
# Extract normalized counts for those genes
mat <- counts(dds, normalized=T)[rownames(sigs_combined),] # or mat <- assay(vsd)[rownames(sigs_combined), ]
# Z-score transform each gene across samples
mat.z <- t(apply(mat, 1, scale))
colnames(mat.z) <- rownames(colData) 
tissue <- colData$tissue_type

# Color map
tissue_color <- c(
  "primary tumor" = "darkorange",
  "normal" = "forestgreen",
  "colorectal cancer metastatic in the liver" = "firebrick"
)

ha <- HeatmapAnnotation(
  Tissue = tissue,
  col = list(Tissue = tissue_color)
)
# Plot Heatmap
Heatmap(
  mat.z,
  name = "Z-score",
  top_annotation = ha,
  cluster_rows = TRUE,
  cluster_columns = TRUE,
  show_column_names = TRUE,
  show_row_names = FALSE,
  row_labels = sigs_combined[rownames(mat.z), "symbol"],# useful when above is TRUE
  row_names_gp = gpar(fontsize = 6),
  column_names_gp = gpar(fontsize = 6),
  heatmap_legend_param = list(title = "Z-score", legend_direction = "horizontal")
)

# Get top 50 genes from both comparisons
topGenes1 <- head(sigs1.df[order(abs(sigs1.df$log2FoldChange), decreasing = TRUE), ], 50)
topGenes2 <- head(sigs2.df[order(abs(sigs2.df$log2FoldChange), decreasing = TRUE), ], 50)
# Combine both sets
topGenes_combined <- rbind(topGenes1, topGenes2)
# Get unique ENSEMBL IDs
unique_genes <- unique(c(rownames(topGenes1), rownames(topGenes2)))
# Subset by unique rownames
topGenes_combined <- topGenes_combined[unique_genes, ]
# Extract normalized counts for those genes
topmat <- counts(dds, normalized=T)[rownames(topGenes_combined),] 
topmat.z <- t(apply(topmat, 1, scale)) # Z score transformation of genes across samples.
colnames(topmat.z) <- rownames(colData)
Heatmap(
  topmat.z,
  name = "Z-score",
  top_annotation = ha,
  cluster_rows = TRUE,
  cluster_columns = TRUE,
  show_column_names = TRUE,
  show_row_names = TRUE,
  row_labels = topGenes_combined[rownames(topmat.z), "symbol"],
  row_names_gp = gpar(fontsize = 6),
  column_names_gp = gpar(fontsize = 6),
  heatmap_legend_param = list(title = "Z-score", legend_direction = "horizontal")
)

LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7cn0NCiMgTG9hZCBwYWNrYWdlcw0KbGlicmFyeShERVNlcTIpICMg4oCYMS40Ni4w4oCZDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShvcmcuSHMuZWcuZGIpICMgbGlicmFyeShvcmcuTW0uZWcuZGIpIGZvciBtb3VzZQ0KbGlicmFyeShDb21wbGV4SGVhdG1hcCkNCmxpYnJhcnkoY2lyY2xpemUpICAgIyBmb3IgY29sb3Igc2NhbGVzDQpsaWJyYXJ5KGdyaWQpICAgICAgICMgZm9yIGdwYXIoKQ0KbGlicmFyeShFbmhhbmNlZFZvbGNhbm8pDQpgYGANCg0KYGBge3J9DQojIExvYWQgY291bnRfbWF0cml4DQphcmdzKHJlYWQuZGVsaW0pICMgaGVhZGVyID0gVFJVRSBieSBkZWZhdWx0DQpyYXdDb3VudHMgPC0gcmVhZC5kZWxpbSgiaHR0cDovL2dlbm9tZWRhdGEub3JnL2dlbi12aXotd29ya3Nob3AvaW50cm9fdG9fZGVzZXEyL3R1dG9yaWFsL0UtR0VPRC01MDc2MC1yYXctY291bnRzLnRzdiIpDQpgYGANCg0KYGBge3J9DQojIEZvcm1hdCByYXdDb3VudHMgYXMgcmVxdWlyZWQgYnkgREVTZXEyDQpgYGANCg0KYGBge3J9DQojIDEuIFNldCBnZW5lIElEIGNvbHVtbiBhcyByb3duYW1lDQpyb3duYW1lcyhyYXdDb3VudHMpIDwtIHJhd0NvdW50cyRHZW5lLklEDQoNCiMgREVTZXEyIGV4cGVjdHMgdGhlIHJhd0NvdW50cyB0byBjb250YWluIG9ubHkgcmF3LCB1bm5vcm1hbGl6ZWQgaW50ZWdlciB2YWx1ZXMsIHdpdGggbm8gbm9uLW51bWVyaWMgY29sdW1ucy4NCiMgMi4gUmVtb3ZlIGNvbHVtbnMgd2l0aCBub24gbnVtZXJpY2FsIHZhbHVlcyANCnJhd0NvdW50c19ia3VwIDwtIHJhd0NvdW50cw0KcmF3Q291bnRzIDwtIHJhd0NvdW50c1ssIC1jKDEsIDIpXQ0KDQpjbGFzcyhyYXdDb3VudHMpICMgYSBkYXRhLmZyYW1lDQojIDMuIENvbnZlcnQgcmF3Q291bnRzIGFzIGEgbWF0cml4DQpyYXdDb3VudHMgPC0gYXMubWF0cml4KHJhd0NvdW50cykNCmBgYA0KYGBge3J9DQojIE5vdGU6IHdlIGNhbiBkbyBib3RoIHN0ZXBzIGFib3ZlIGluIG9uZSBsaW5lIG9mIGNvZGUNCiMgcmF3Q291bnRzIDwtIGFzLm1hdHJpeChyYXdDb3VudHNbLCBzYXBwbHkocmF3Q291bnRzLCBpcy5udW1lcmljKV0pDQpgYGANCg0KYGBge3J9DQojIGNoZWNrcw0KaXMubWF0cml4KHJhd0NvdW50cykgIyBzaG91bGQgYmUgVFJVRQ0KaXMubnVtZXJpYyhyYXdDb3VudHMpICMgc2hvdWxkIGJlIFRSVUUNCmBgYA0KDQpgYGB7cn0NCiMgIyBGaWx0ZXJpbmc6IHJlbW92ZSBnZW5lcyB3aXRoIDAgcmVhZHMgaW4gYWxsIHNhbXBsZXMuDQpkaW0ocmF3Q291bnRzKQ0KZGltKHJhd0NvdW50c1t3aGljaChyb3dTdW1zKHJhd0NvdW50cyk+MCksXSkNCnJhd0NvdW50cyA8LSByYXdDb3VudHNbd2hpY2gocm93U3VtcyhyYXdDb3VudHMpPjApLF0NCmBgYA0KYGBge3J9DQojIExvYWQgY29sRGF0YSAoYSBkYXRhZnJhbWUgZGVzY3JpYmluZyBzYW1wbGVzKQ0KY29sRGF0YSA8LSByZWFkLmRlbGltKCJodHRwOi8vZ2Vub21lZGF0YS5vcmcvZ2VuLXZpei13b3Jrc2hvcC9pbnRyb190b19kZXNlcTIvdHV0b3JpYWwvRS1HRU9ELTUwNzYwLWV4cGVyaW1lbnQtZGVzaWduLnRzdiIpDQpgYGANCg0KYGBge3J9DQojIEZvcm1hdCBjb2xEYXRhIGFzIHJlcXVpcmVkIGJ5IERFU2VxMg0KYGBgDQoNCmBgYHtyfQ0KIyAxLiBTZXQgc2FtcGxlIElEIChoZXJlIFJ1biBjb2x1bW4pIGFzIHJvd25hbWUNCnJvd25hbWVzKGNvbERhdGEpIDwtIGNvbERhdGEkUnVuDQoNCiMgMi4gUmVtb3ZlIHVubmVjZXNzYXJ5IGNvbHVtbnMNCiMgVGlzc3VlIHR5cGUgaXMgdGhlIHByaW1hcnkgY29uZGl0aW9uIG9mIGludGVyZXN0DQojIEluZGl2aWR1YWwgSURzIGFyZSB0cmVhdGVkIGFzIGNvbnRyb2wgdmFyaWFibGVzIHRvIGFjY291bnQgZm9yIGludGVyLWluZGl2aWR1YWwgdmFyaWF0aW9uDQprZWVwIDwtIGMoIlNhbXBsZS5DaGFyYWN0ZXJpc3RpYy5iaW9wc3kuc2l0ZS4iLCAiU2FtcGxlLkNoYXJhY3RlcmlzdGljLmluZGl2aWR1YWwuIikNCmNvbERhdGEgPC0gY29sRGF0YVssIGtlZXBdDQoNCiMgMy4gUmVuYW1lIGNvbHVtbnMNCmNvbG5hbWVzKGNvbERhdGEpIDwtIGMoInRpc3N1ZV90eXBlIiwgImluZGl2aWR1YWxfaWQiKQ0KDQojICA0LiBWZXJpZnkgdGhhdCB0aGUgc2FtcGxlIElEcyBtYXRjaCBleGFjdGx5IGFuZCBhcmUgaW4gdGhlIHNhbWUgb3JkZXIgYmV0d2VlbiByYXdDb3VudHMgYW5kIGNvbERhdGENCmFsbChyb3duYW1lcyhjb2xEYXRhKSA9PSBjb2xuYW1lcyhyYXdDb3VudHMpKSAjIHNob3VsZCByZXR1cm4gVFJVRQ0KDQpoZWFkKGNvbERhdGEpDQpgYGANCmBgYHtyfQ0KIyA1LiBjb252ZXJ0IGJvdGggY29sdW1ucyBvZiBjb2xEYXRhIGFzIGZhY3RvcnMNCmNsYXNzKGNvbERhdGEkaW5kaXZpZHVhbF9pZCkgIyAiY2hhcmFjdGVyIg0KY29sRGF0YSRpbmRpdmlkdWFsX2lkIDwtIGZhY3Rvcihjb2xEYXRhJGluZGl2aWR1YWxfaWQpDQpjbGFzcyhjb2xEYXRhJGluZGl2aWR1YWxfaWQpICMgImZhY3RvciINCmNvbERhdGEkdGlzc3VlX3R5cGUgPC0gZmFjdG9yKGNvbERhdGEkdGlzc3VlX3R5cGUpDQpgYGANCmBgYHtyfQ0KIyBERVNlcTIgdXNlcyB0aGUgZmlyc3QgZmFjdG9yIGxldmVsIGFzIHJlZmVyZW5jZQ0KbGV2ZWxzKGNvbERhdGEkdGlzc3VlX3R5cGUpICAjIHJlZmVyZW5jZSA9ICJjb2xvcmVjdGFsIGNhbmNlciBtZXRhc3RhdGljIGluIHRoZSBsaXZlciINCiMgQ29tcGFyaXNvbnM6ICJub3JtYWwiIGFuZCAicHJpbWFyeSB0dW1vciIgdnMgcmVmZXJlbmNlDQpgYGANCmBgYHtyfQ0KIyBXZSB3YW50IHRvIGNvbXBhcmUgImNvbG9yZWN0YWwgY2FuY2VyIG1ldGFzdGF0aWMgaW4gdGhlIGxpdmVyIiBhbmQgICJwcmltYXJ5IHR1bW9yIiBhZ2FpbnN0ICJub3JtYWwiIGluc3RlYWQNCiMgNi4gQ2hhbmdlIGxldmVscyBvcmRlciBzdWNoIHRoYXQgIHJlZmVyZW5jZSA9ICJub3JtYWwiDQpsZXZlbHMoY29sRGF0YSR0aXNzdWVfdHlwZSkgPC0gYygNCiAgIm5vcm1hbCIsDQogICJwcmltYXJ5IHR1bW9yIiwNCiAgImNvbG9yZWN0YWwgY2FuY2VyIG1ldGFzdGF0aWMgaW4gdGhlIGxpdmVyIg0KKQ0KbGV2ZWxzKGNvbERhdGEkdGlzc3VlX3R5cGUpDQpgYGANCmBgYHtyfQ0KIyBDcmVhdGUgdGhlIERFc2VxMkRhdGFTZXQgb2JqZWN0DQojIERlc2lnbjogY29udHJvbCB2YXJpYWJsZSBmaXJzdCwgY29uZGl0aW9uIG9mIGludGVyZXN0IHNlY29uZA0KIyBERVNlcTIgYWRqdXN0cyBmb3IgaW5kaXZpZHVhbCB2YXJpYXRpb24gYmVmb3JlIHRlc3RpbmcgdGlzc3VlX3R5cGUgZGlmZmVyZW5jZXMNCmRkcyA8LSBERVNlcURhdGFTZXRGcm9tTWF0cml4KGNvdW50RGF0YSA9IHJhd0NvdW50cywgY29sRGF0YSA9IGNvbERhdGEsIGRlc2lnbiA9IH4gaW5kaXZpZHVhbF9pZCArIHRpc3N1ZV90eXBlKQ0KDQpgYGANCg0KYGBge3J9DQpkZHMgIyBwcmludHMgc3VtbWFyeSBvZiB0aGUgREVTZXFEYXRhU2V0DQoNCmBgYA0KYGBge3J9DQpjb2xEYXRhKGRkcykgICMgc2hvd3Mgc2FtcGxlIGFubm90YXRpb25zIChlLmcuLCB0aXNzdWVfdHlwZSwgaW5kaXZpZHVhbF9pZCkNCg0KYGBgDQpgYGB7cn0NCmNvdW50cyhkZHMpWzE6NSwgMTo1XSAgIyB2aWV3IGZpcnN0IGZldyBnZW5lcyBhbmQgc2FtcGxlcw0KYGBgDQpgYGB7cn0NCg0KIyAgUnVuIHRoZSBERVNlcSBwaXBlbGluZQ0KZGRzIDwtIERFU2VxKGRkcykNCmBgYA0KDQpgYGB7cn0NCiMgVmFyaWFuY2UgU3RhYmlsaXppbmcgVHJhbnNmb3JtYXRpb24gYW5kICBwbG90IFBDQQ0KdnNkIDwtIHZzdChkZHMsIGJsaW5kID0gRkFMU0UpDQpwbG90UENBKHZzZCwgaW50Z3JvdXA9InRpc3N1ZV90eXBlIikrIHRoZW1lX2dyZXkoKQ0KYGBgDQpgYGB7cn0NCiMgTm90ZTogY29udHJhc3QgPSBjKGZhY3RvciwgbnVtZXJhdG9yLCBkZW5vbWluYXRvcikNCiMgbWVhbnMgQ29tcHV0ZSBsb2cyIGZvbGQgY2hhbmdlIGFzIG51bWVyYXRvciAvIGRlbm9taW5hdG9yLg0KIyBjb250cmFzdCA9IGMoInRpc3N1ZV90eXBlIiwgInByaW1hcnkgdHVtb3IiLCAibm9ybWFsIik6IHBvc2l0aXZlIHZhbHVlcyBtZWFuIGhpZ2hlciBpbiB0dW1vciwgbmVnYXRpdmUgbWVhbiBoaWdoZXIgaW4gbm9ybWFsDQojIGNvbnRyYXN0ID0gYygidGlzc3VlX3R5cGUiLCAibm9ybWFsIiwgInByaW1hcnkgdHVtb3IiKTogcG9zaXRpdmUgdmFsdWVzIG1lYW4gaGlnaGVyIGluIG5vcm1hbCwgbmVnYXRpdmUgbWVhbiBoaWdoZXIgaW4gdHVtb3INCmBgYA0KDQpgYGB7cn0NCiMgRXh0cmFjdCByZXN1bHRzDQpyZXMxIDwtIHJlc3VsdHMoZGRzLCBjb250cmFzdCA9IGMoInRpc3N1ZV90eXBlIiwgInByaW1hcnkgdHVtb3IiLCAibm9ybWFsIikpDQpyZXMxDQpgYGANCmBgYHtyfQ0KIyBSZW1vdmUgcm93cyB3aXRoIE5BIA0KcmVzMSA8LSBuYS5vbWl0KHJlczEpDQpyZXMxLmRmIDwtIGFzLmRhdGEuZnJhbWUocmVzMSkNCnJlczEuZGYNCmBgYA0KYGBge3J9DQojIGdldCBnZW5lIG5hbWVzIGZvciBnZW5lIElEcw0KcmVzMS5kZiRzeW1ib2wgPC0gbWFwSWRzKG9yZy5Icy5lZy5kYiwga2V5cyA9IHJvd25hbWVzKHJlczEuZGYpLCBrZXl0eXBlID0gIkVOU0VNQkwiLCBjb2x1bW4gPSAiU1lNQk9MIikNCnJlczEuZGYgPC0gcmVzMS5kZlshaXMubmEocmVzMS5kZiRzeW1ib2wpLF0NCg0KYGBgDQoNCmBgYHtyfQ0KcmVzMiA8LSByZXN1bHRzKGRkcywgY29udHJhc3QgPSBjKCJ0aXNzdWVfdHlwZSIsICJjb2xvcmVjdGFsIGNhbmNlciBtZXRhc3RhdGljIGluIHRoZSBsaXZlciIsICJub3JtYWwiKSkNCnJlczIgPC0gbmEub21pdChyZXMyKQ0KcmVzMi5kZiA8LSBhcy5kYXRhLmZyYW1lKHJlczIpDQpyZXMyLmRmDQpyZXMyLmRmJHN5bWJvbCA8LSBtYXBJZHMob3JnLkhzLmVnLmRiLCBrZXlzID0gcm93bmFtZXMocmVzMi5kZiksIGtleXR5cGUgPSAiRU5TRU1CTCIsIGNvbHVtbiA9ICJTWU1CT0wiKQ0KcmVzMi5kZiA8LSByZXMyLmRmWyFpcy5uYShyZXMyLmRmJHN5bWJvbCksXQ0KYGBgDQoNCmBgYHtyfQ0KIyBWb2xjYW5vIHBsb3RzIDENCkVuaGFuY2VkVm9sY2FubyhyZXMxLmRmLCB4ID0gJ2xvZzJGb2xkQ2hhbmdlJywgeSA9ICdwYWRqJywgbGFiID0gcmVzMS5kZiRzeW1ib2wsDQogICAgICAgICAgICAgICAgcEN1dG9mZiA9IDAuMDUsIEZDY3V0b2ZmID0gMSkNCg0KYGBgDQpgYGB7cn0NCiMgVm9sY2FubyBwbG90cyAyDQpFbmhhbmNlZFZvbGNhbm8ocmVzMi5kZiwgeCA9ICdsb2cyRm9sZENoYW5nZScsIHkgPSAncGFkaicsIGxhYiA9IHJlczIuZGYkc3ltYm9sLA0KICAgICAgICAgICAgICAgIHBDdXRvZmYgPSAwLjA1LCBGQ2N1dG9mZiA9IDEpDQpgYGANCmBgYHtyfQ0KIyBWb2xjYW5vIHBsb3RzIHVzaW5nIGdncGxvdDINCg0KcmVzMS5kZiRleHByZXNzaW9uIDwtICJOT1QiDQpyZXMxLmRmJGV4cHJlc3Npb25bcmVzMS5kZiRwYWRqIDwgMC4wMSAmIHJlczEuZGYkbG9nMkZvbGRDaGFuZ2UgPiAyXSA8LSAiVVAiDQpyZXMxLmRmJGV4cHJlc3Npb25bcmVzMS5kZiRwYWRqIDwgMC4wMSAmIHJlczEuZGYkbG9nMkZvbGRDaGFuZ2UgPCAtMl0gPC0gIkRPV04iDQoNCmdncGxvdChkYXRhID0gcmVzMS5kZiwgYWVzKHggPSBsb2cyRm9sZENoYW5nZSwgeSA9IC1sb2cxMChwYWRqKSwgY29sID0gZXhwcmVzc2lvbikpICsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gYygtMiwgMiksIGNvbCA9ICJncmF5IiwgbGluZXR5cGUgPSAnZGFzaGVkJykgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAtbG9nMTAoMC4wMSksIGNvbCA9ICJncmF5IiwgbGluZXR5cGUgPSAnZGFzaGVkJykgKw0KICBnZW9tX3BvaW50KHNpemUgPSAyKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCIjMzQ5OERCIiwgImdyZXkiLCAiI0U3NEMzQyIpLCANCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIkRvd25yZWd1bGF0ZWQiLCAiTm90IHNpZ25pZmljYW50IiwgIlVwcmVndWxhdGVkIikpICsNCiAgdGhlbWVfY2xhc3NpYygpDQpgYGANCmBgYHtyfQ0KcmVzMi5kZiRleHByZXNzaW9uIDwtICJOT1QiDQpyZXMyLmRmJGV4cHJlc3Npb25bcmVzMi5kZiRwYWRqIDwgMC4wMSAmIHJlczIuZGYkbG9nMkZvbGRDaGFuZ2UgPiAyXSA8LSAiVVAiDQpyZXMyLmRmJGV4cHJlc3Npb25bcmVzMi5kZiRwYWRqIDwgMC4wMSAmIHJlczIuZGYkbG9nMkZvbGRDaGFuZ2UgPCAtMl0gPC0gIkRPV04iDQoNCmdncGxvdChkYXRhID0gcmVzMi5kZiwgYWVzKHggPSBsb2cyRm9sZENoYW5nZSwgeSA9IC1sb2cxMChwYWRqKSwgY29sID0gZXhwcmVzc2lvbikpICsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gYygtMiwgMiksIGNvbCA9ICJncmF5IiwgbGluZXR5cGUgPSAnZGFzaGVkJykgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAtbG9nMTAoMC4wMSksIGNvbCA9ICJncmF5IiwgbGluZXR5cGUgPSAnZGFzaGVkJykgKw0KICBnZW9tX3BvaW50KHNpemUgPSAyKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCIjMzQ5OERCIiwgImdyZXkiLCAiI0U3NEMzQyIpLCANCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIkRvd25yZWd1bGF0ZWQiLCAiTm90IHNpZ25pZmljYW50IiwgIlVwcmVndWxhdGVkIikpICsNCiAgdGhlbWVfY2xhc3NpYygpDQpgYGANCg0KYGBge3J9DQojIEZpbmQgc2lnbmlmaWNhbnQgZ2VuZXMNCnNpZ3MxLmRmIDwtIHJlczEuZGZbcmVzMS5kZiRwYWRqIDwgMC4wMSxdDQpzaWdzMS5kZiA8LSBzaWdzMS5kZlsoYWJzKHNpZ3MxLmRmJGxvZzJGb2xkQ2hhbmdlKSA+IDIpLF0NCnNpZ3MxLmRmIDwtIHNpZ3MxLmRmW3NpZ3MxLmRmJGJhc2VNZWFuID4gNTAsXSAjIC0gIGF2ZXJhZ2Ugbm9ybWFsaXplZCBjb3VudCBmb3IgYSBnZW5lIGFjcm9zcyBhbGwgc2FtcGxlcw0Kc2lnczEuZGYNCg0KDQpgYGANCg0KYGBge3J9DQpzaWdzMi5kZiA8LSByZXMyLmRmW3JlczIuZGYkcGFkaiA8IDAuMDEsXQ0Kc2lnczIuZGYgPC0gc2lnczIuZGZbKGFicyhzaWdzMi5kZiRsb2cyRm9sZENoYW5nZSkgPiAyKSxdDQpzaWdzMi5kZiA8LSBzaWdzMi5kZltzaWdzMi5kZiRiYXNlTWVhbiA+IDUwLF0gDQpzaWdzMi5kZg0KYGBgDQoNCmBgYHtyfQ0KIyBDb21iaW5lIHNpZ25pZmljYW50IGdlbmVzIGZyb20gYm90aCBjb21wYXJpc29ucw0Kc2lnc19jb21iaW5lZCA8LSByYmluZChzaWdzMS5kZiwgc2lnczIuZGYpDQojIEdldCB1bmlxdWUgZ2VuZSBJRHMNCnVuaXF1ZV9nZW5lcyA8LSB1bmlxdWUoYyhyb3duYW1lcyhzaWdzMS5kZiksIHJvd25hbWVzKHNpZ3MyLmRmKSkpDQojIFN1YnNldCBieSB1bmlxdWUgcm93bmFtZXMNCnNpZ3NfY29tYmluZWQgPC0gc2lnc19jb21iaW5lZFt1bmlxdWVfZ2VuZXMsIF0NCmBgYA0KDQpgYGB7cn0NCiMgRXh0cmFjdCBub3JtYWxpemVkIGNvdW50cyBmb3IgdGhvc2UgZ2VuZXMNCm1hdCA8LSBjb3VudHMoZGRzLCBub3JtYWxpemVkPVQpW3Jvd25hbWVzKHNpZ3NfY29tYmluZWQpLF0gIyBvciBtYXQgPC0gYXNzYXkodnNkKVtyb3duYW1lcyhzaWdzX2NvbWJpbmVkKSwgXQ0KIyBaLXNjb3JlIHRyYW5zZm9ybSBlYWNoIGdlbmUgYWNyb3NzIHNhbXBsZXMNCm1hdC56IDwtIHQoYXBwbHkobWF0LCAxLCBzY2FsZSkpDQpjb2xuYW1lcyhtYXQueikgPC0gcm93bmFtZXMoY29sRGF0YSkgDQpgYGANCg0KYGBge3J9DQp0aXNzdWUgPC0gY29sRGF0YSR0aXNzdWVfdHlwZQ0KDQojIENvbG9yIG1hcA0KdGlzc3VlX2NvbG9yIDwtIGMoDQogICJwcmltYXJ5IHR1bW9yIiA9ICJkYXJrb3JhbmdlIiwNCiAgIm5vcm1hbCIgPSAiZm9yZXN0Z3JlZW4iLA0KICAiY29sb3JlY3RhbCBjYW5jZXIgbWV0YXN0YXRpYyBpbiB0aGUgbGl2ZXIiID0gImZpcmVicmljayINCikNCg0KaGEgPC0gSGVhdG1hcEFubm90YXRpb24oDQogIFRpc3N1ZSA9IHRpc3N1ZSwNCiAgY29sID0gbGlzdChUaXNzdWUgPSB0aXNzdWVfY29sb3IpDQopDQpgYGANCg0KYGBge3J9DQojIFBsb3QgSGVhdG1hcA0KSGVhdG1hcCgNCiAgbWF0LnosDQogIG5hbWUgPSAiWi1zY29yZSIsDQogIHRvcF9hbm5vdGF0aW9uID0gaGEsDQogIGNsdXN0ZXJfcm93cyA9IFRSVUUsDQogIGNsdXN0ZXJfY29sdW1ucyA9IFRSVUUsDQogIHNob3dfY29sdW1uX25hbWVzID0gVFJVRSwNCiAgc2hvd19yb3dfbmFtZXMgPSBGQUxTRSwNCiAgcm93X2xhYmVscyA9IHNpZ3NfY29tYmluZWRbcm93bmFtZXMobWF0LnopLCAic3ltYm9sIl0sIyB1c2VmdWwgd2hlbiBhYm92ZSBpcyBUUlVFDQogIHJvd19uYW1lc19ncCA9IGdwYXIoZm9udHNpemUgPSA2KSwNCiAgY29sdW1uX25hbWVzX2dwID0gZ3Bhcihmb250c2l6ZSA9IDYpLA0KICBoZWF0bWFwX2xlZ2VuZF9wYXJhbSA9IGxpc3QodGl0bGUgPSAiWi1zY29yZSIsIGxlZ2VuZF9kaXJlY3Rpb24gPSAiaG9yaXpvbnRhbCIpDQopDQpgYGANCmBgYHtyfQ0KIyBHZXQgdG9wIDUwIGdlbmVzIGZyb20gYm90aCBjb21wYXJpc29ucw0KdG9wR2VuZXMxIDwtIGhlYWQoc2lnczEuZGZbb3JkZXIoYWJzKHNpZ3MxLmRmJGxvZzJGb2xkQ2hhbmdlKSwgZGVjcmVhc2luZyA9IFRSVUUpLCBdLCA1MCkNCnRvcEdlbmVzMiA8LSBoZWFkKHNpZ3MyLmRmW29yZGVyKGFicyhzaWdzMi5kZiRsb2cyRm9sZENoYW5nZSksIGRlY3JlYXNpbmcgPSBUUlVFKSwgXSwgNTApDQpgYGANCg0KYGBge3J9DQojIENvbWJpbmUgYm90aCBzZXRzDQp0b3BHZW5lc19jb21iaW5lZCA8LSByYmluZCh0b3BHZW5lczEsIHRvcEdlbmVzMikNCiMgR2V0IHVuaXF1ZSBFTlNFTUJMIElEcw0KdW5pcXVlX2dlbmVzIDwtIHVuaXF1ZShjKHJvd25hbWVzKHRvcEdlbmVzMSksIHJvd25hbWVzKHRvcEdlbmVzMikpKQ0KIyBTdWJzZXQgYnkgdW5pcXVlIHJvd25hbWVzDQp0b3BHZW5lc19jb21iaW5lZCA8LSB0b3BHZW5lc19jb21iaW5lZFt1bmlxdWVfZ2VuZXMsIF0NCmBgYA0KDQpgYGB7cn0NCiMgRXh0cmFjdCBub3JtYWxpemVkIGNvdW50cyBmb3IgdGhvc2UgZ2VuZXMNCnRvcG1hdCA8LSBjb3VudHMoZGRzLCBub3JtYWxpemVkPVQpW3Jvd25hbWVzKHRvcEdlbmVzX2NvbWJpbmVkKSxdIA0KdG9wbWF0LnogPC0gdChhcHBseSh0b3BtYXQsIDEsIHNjYWxlKSkgIyBaIHNjb3JlIHRyYW5zZm9ybWF0aW9uIG9mIGdlbmVzIGFjcm9zcyBzYW1wbGVzLg0KY29sbmFtZXModG9wbWF0LnopIDwtIHJvd25hbWVzKGNvbERhdGEpDQpgYGANCg0KYGBge3J9DQpIZWF0bWFwKA0KICB0b3BtYXQueiwNCiAgbmFtZSA9ICJaLXNjb3JlIiwNCiAgdG9wX2Fubm90YXRpb24gPSBoYSwNCiAgY2x1c3Rlcl9yb3dzID0gVFJVRSwNCiAgY2x1c3Rlcl9jb2x1bW5zID0gVFJVRSwNCiAgc2hvd19jb2x1bW5fbmFtZXMgPSBUUlVFLA0KICBzaG93X3Jvd19uYW1lcyA9IFRSVUUsDQogIHJvd19sYWJlbHMgPSB0b3BHZW5lc19jb21iaW5lZFtyb3duYW1lcyh0b3BtYXQueiksICJzeW1ib2wiXSwNCiAgcm93X25hbWVzX2dwID0gZ3Bhcihmb250c2l6ZSA9IDYpLA0KICBjb2x1bW5fbmFtZXNfZ3AgPSBncGFyKGZvbnRzaXplID0gNiksDQogIGhlYXRtYXBfbGVnZW5kX3BhcmFtID0gbGlzdCh0aXRsZSA9ICJaLXNjb3JlIiwgbGVnZW5kX2RpcmVjdGlvbiA9ICJob3Jpem9udGFsIikNCikNCmBgYA0KDQo=